home *** CD-ROM | disk | FTP | other *** search
/ Aminet 2 / Aminet AMIGA CDROM (1994)(Walnut Creek)[Feb 1994][W.O. 44790-1].iso / Aminet / util / gnu / diff_2_3.lha / diff-2.3 / diff.c < prev    next >
C/C++ Source or Header  |  1993-05-26  |  26KB  |  1,002 lines

  1. /* GNU DIFF main routine.
  2.    Copyright (C) 1988, 1989, 1992 Free Software Foundation, Inc.
  3.  
  4. This file is part of GNU DIFF.
  5.  
  6. GNU DIFF is free software; you can redistribute it and/or modify
  7. it under the terms of the GNU General Public License as published by
  8. the Free Software Foundation; either version 2, or (at your option)
  9. any later version.
  10.  
  11. GNU DIFF is distributed in the hope that it will be useful,
  12. but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14. GNU General Public License for more details.
  15.  
  16. You should have received a copy of the GNU General Public License
  17. along with GNU DIFF; see the file COPYING.  If not, write to
  18. the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */
  19.  
  20. /* GNU DIFF was written by Mike Haertel, David Hayes,
  21.    Richard Stallman, Len Tower, and Paul Eggert.  */
  22.  
  23. #define GDIFF_MAIN
  24. #include "diff.h"
  25. #include "getopt.h"
  26. #include "fnmatch.h"
  27.  
  28. #ifndef DEFAULT_WIDTH
  29. #define DEFAULT_WIDTH 130
  30. #endif
  31.  
  32. #ifndef GUTTER_WIDTH_MINIMUM
  33. #define GUTTER_WIDTH_MINIMUM 3
  34. #endif
  35.  
  36. int diff_dirs ();
  37. int diff_2_files ();
  38.  
  39. static int compare_files ();
  40. static int specify_format ();
  41. static void add_regexp();
  42. static void specify_style ();
  43. static void usage ();
  44. #ifdef AMIGA
  45. static int fake_stat_result ();
  46. #endif /* AMIGA */
  47.  
  48. /* Nonzero for -r: if comparing two directories,
  49.    compare their common subdirectories recursively.  */
  50.  
  51. static int recursive;
  52.  
  53. /* For debugging: don't do discard_confusing_lines.  */
  54.  
  55. int no_discards;
  56.  
  57. /* Return a string containing the command options with which diff was invoked.
  58.    Spaces appear between what were separate ARGV-elements.
  59.    There is a space at the beginning but none at the end.
  60.    If there were no options, the result is an empty string.
  61.  
  62.    Arguments: OPTIONVEC, a vector containing separate ARGV-elements, and COUNT,
  63.    the length of that vector.  */
  64.  
  65. static char *
  66. option_list (optionvec, count)
  67.      char **optionvec;  /* Was `vector', but that collides on Alliant.  */
  68.      int count;
  69. {
  70.   int i;
  71.   int length = 0;
  72.   char *result;
  73.  
  74.   for (i = 0; i < count; i++)
  75.     length += strlen (optionvec[i]) + 1;
  76.  
  77.   result = (char *) xmalloc (length + 1);
  78.   result[0] = 0;
  79.  
  80.   for (i = 0; i < count; i++)
  81.     {
  82.       strcat (result, " ");
  83.       strcat (result, optionvec[i]);
  84.     }
  85.  
  86.   return result;
  87. }
  88.  
  89. /* Convert STR to a positive integer, storing the result in *OUT. 
  90.    If STR is not a valid integer, return -1 (otherwise 0). */
  91. static int
  92. ck_atoi (str, out)
  93.      char *str;
  94.      int *out;
  95. {
  96.   char *p;
  97.   for (p = str; *p; p++)
  98.     if (*p < '0' || *p > '9')
  99.       return -1;
  100.  
  101.   *out = atoi (optarg);
  102.   return 0;
  103. }
  104.  
  105. /* Keep track of excluded file name patterns.  */
  106.  
  107. static const char **exclude;
  108. static int exclude_alloc, exclude_count;
  109.  
  110. int
  111. excluded_filename (f)
  112.      const char *f;
  113. {
  114.   int i;
  115.   for (i = 0;  i < exclude_count;  i++)
  116.     if (fnmatch (exclude[i], f, 0) == 0)
  117.       return 1;
  118.   return 0;
  119. }
  120.  
  121. static void
  122. add_exclude (pattern)
  123.      const char *pattern;
  124. {
  125.   if (exclude_alloc <= exclude_count)
  126.     exclude = (const char **)
  127.           (exclude_alloc == 0
  128.            ? xmalloc ((exclude_alloc = 64) * sizeof (*exclude))
  129.            : xrealloc (exclude, (exclude_alloc *= 2) * sizeof (*exclude)));
  130.  
  131.   exclude[exclude_count++] = pattern;
  132. }
  133.  
  134. static int
  135. add_exclude_file (name)
  136.      const char *name;
  137. {
  138.   struct file_data f;
  139.   char *p, *q, *lim;
  140.  
  141.   f.name = optarg;
  142.   f.desc = strcmp (optarg, "-") == 0 ? 0 : open (optarg, O_RDONLY, 0);
  143.   if (f.desc < 0 || fstat (f.desc, &f.stat) != 0)
  144.     return -1;
  145.  
  146.   sip (&f, 1);
  147.   slurp (&f);
  148.  
  149.   for (p = f.buffer, lim = p + f.buffered_chars;  p < lim;  p = q)
  150.     {
  151.       q = memchr (p, '\n', lim - p);
  152.       if (!q)
  153.     q = lim;
  154.       *q++ = 0;
  155.       add_exclude (p);
  156.     }
  157.  
  158.   return close (f.desc);
  159. }
  160.  
  161. /* The numbers 129- that appear in the fourth element of some entries
  162.    tell the big switch in `main' how to process those options.  */
  163.  
  164. static struct option longopts[] =
  165. {
  166.   {"ignore-blank-lines", 0, 0, 'B'},
  167.   {"context", 2, 0, 'C'},
  168.   {"ifdef", 1, 0, 'D'},
  169.   {"show-function-line", 1, 0, 'F'},
  170.   {"speed-large-files", 0, 0, 'H'},
  171.   {"ignore-matching-lines", 1, 0, 'I'},
  172.   {"label", 1, 0, 'L'},
  173.   {"file-label", 1, 0, 'L'},    /* An alias, no longer recommended */
  174.   {"new-file", 0, 0, 'N'},
  175.   {"entire-new-file", 0, 0, 'N'},    /* An alias, no longer recommended */
  176.   {"unidirectional-new-file", 0, 0, 'P'},
  177.   {"starting-file", 1, 0, 'S'},
  178.   {"initial-tab", 0, 0, 'T'},
  179.   {"width", 1, 0, 'W'},
  180.   {"text", 0, 0, 'a'},
  181.   {"ascii", 0, 0, 'a'},        /* An alias, no longer recommended */
  182.   {"ignore-space-change", 0, 0, 'b'},
  183.   {"minimal", 0, 0, 'd'},
  184.   {"ed", 0, 0, 'e'},
  185.   {"forward-ed", 0, 0, 'f'},
  186.   {"ignore-case", 0, 0, 'i'},
  187.   {"paginate", 0, 0, 'l'},
  188.   {"print", 0, 0, 'l'},        /* An alias, no longer recommended */
  189.   {"rcs", 0, 0, 'n'},
  190.   {"show-c-function", 0, 0, 'p'},
  191.   {"binary", 0, 0, 'q'},    /* An alias, no longer recommended */
  192.   {"brief", 0, 0, 'q'},
  193.   {"recursive", 0, 0, 'r'},
  194.   {"report-identical-files", 0, 0, 's'},
  195.   {"expand-tabs", 0, 0, 't'},
  196.   {"version", 0, 0, 'v'},
  197.   {"ignore-all-space", 0, 0, 'w'},
  198.   {"exclude", 1, 0, 'x'},
  199.   {"exclude-from", 1, 0, 'X'},
  200.   {"side-by-side", 0, 0, 'y'},
  201.   {"unified", 2, 0, 'U'},
  202.   {"left-column", 0, 0, 129},
  203.   {"suppress-common-lines", 0, 0, 130},
  204.   {"sdiff-merge-assist", 0, 0, 131},
  205.   {"old-line-format", 1, 0, 132},
  206.   {"new-line-format", 1, 0, 133},
  207.   {"unchanged-line-format", 1, 0, 134},
  208.   {"old-group-format", 1, 0, 135},
  209.   {"new-group-format", 1, 0, 136},
  210.   {"unchanged-group-format", 1, 0, 137},
  211.   {"changed-group-format", 1, 0, 138},
  212.   {"horizon-lines", 1, 0, 139},
  213.   {0, 0, 0, 0}
  214. };
  215.  
  216. int
  217. main (argc, argv)
  218.      int argc;
  219.      char *argv[];
  220. {
  221.   int val;
  222.   int c;
  223.   int prev = -1;
  224.   extern char *version_string;
  225.   int width = DEFAULT_WIDTH;
  226.  
  227.   program = argv[0];
  228.  
  229.   /* Do our initializations. */
  230.   output_style = OUTPUT_NORMAL;
  231.   always_text_flag = FALSE;
  232.   ignore_space_change_flag = FALSE;
  233.   ignore_all_space_flag = FALSE;
  234.   length_varies = FALSE;
  235.   ignore_case_flag = FALSE;
  236.   ignore_blank_lines_flag = FALSE;
  237.   ignore_regexp_list = NULL;
  238.   function_regexp_list = NULL;
  239.   print_file_same_flag = FALSE;
  240.   entire_new_file_flag = FALSE;
  241.   unidirectional_new_file_flag = FALSE;
  242.   no_details_flag = FALSE;
  243.   context = -1;
  244.   line_end_char = '\n';
  245.   tab_align_flag = FALSE;
  246.   tab_expand_flag = FALSE;
  247.   recursive = FALSE;
  248.   paginate_flag = FALSE;
  249.   heuristic = FALSE;
  250.   dir_start_file = NULL;
  251.   msg_chain = NULL;
  252.   msg_chain_end = NULL;
  253.   no_discards = 0;
  254.  
  255.   /* Decode the options.  */
  256.  
  257.   while ((c = getopt_long (argc, argv,
  258.                "0123456789abBcC:dD:efF:hHiI:lL:nNpPqrsS:tTuU:vwW:x:X:y",
  259.                longopts, (int *)0)) != EOF)
  260.     {
  261.       switch (c)
  262.     {
  263.       /* All digits combine in decimal to specify the context-size.  */
  264.     case '1':
  265.     case '2':
  266.     case '3':
  267.     case '4':
  268.     case '5':
  269.     case '6':
  270.     case '7':
  271.     case '8':
  272.     case '9':
  273.     case '0':
  274.       if (context == -1)
  275.         context = 0;
  276.       /* If a context length has already been specified,
  277.          more digits allowed only if they follow right after the others.
  278.          Reject two separate runs of digits, or digits after -C.  */
  279.       else if (prev < '0' || prev > '9')
  280.         fatal ("context length specified twice");
  281.  
  282.       context = context * 10 + c - '0';
  283.       break;
  284.  
  285.     case 'a':
  286.       /* Treat all files as text files; never treat as binary.  */
  287.       always_text_flag = 1;
  288.       break;
  289.  
  290.     case 'b':
  291.       /* Ignore changes in amount of whitespace.  */
  292.       ignore_space_change_flag = 1;
  293.       length_varies = 1;
  294.       break;
  295.  
  296.     case 'B':
  297.       /* Ignore changes affecting only blank lines.  */
  298.       ignore_blank_lines_flag = 1;
  299.       break;
  300.  
  301.     case 'C':        /* +context[=lines] */
  302.     case 'U':        /* +unified[=lines] */
  303.       if (optarg)
  304.         {
  305.           if (context >= 0)
  306.         fatal ("context length specified twice");
  307.  
  308.           if (ck_atoi (optarg, &context))
  309.         fatal ("invalid context length argument");
  310.         }
  311.  
  312.       /* Falls through.  */
  313.     case 'c':
  314.       /* Make context-style output.  */
  315.       specify_style (c == 'U' ? OUTPUT_UNIFIED : OUTPUT_CONTEXT);
  316.       break;
  317.  
  318.     case 'd':
  319.       /* Don't discard lines.  This makes things slower (sometimes much
  320.          slower) but will find a guaranteed minimal set of changes.  */
  321.       no_discards = 1;
  322.       break;
  323.  
  324.     case 'D':
  325.       /* Make merged #ifdef output.  */
  326.       specify_style (OUTPUT_IFDEF);
  327.       {
  328.         int i, err = 0;
  329.         static const char C_ifdef_group_formats[] =
  330.           "#ifndef %s\n%%<#endif /* not %s */\n%c#ifdef %s\n%%>#endif /* %s */\n%c%%=%c#ifndef %s\n%%<#else /* %s */\n%%>#endif /* %s */\n";
  331.         char *b = xmalloc (sizeof (C_ifdef_group_formats)
  332.                    + 7 * strlen(optarg) - 14 /* 7*"%s" */
  333.                    - 8 /* 5*"%%" + 3*"%c" */);
  334.         sprintf (b, C_ifdef_group_formats,
  335.              optarg, optarg, 0,
  336.              optarg, optarg, 0, 0,
  337.              optarg, optarg, optarg);
  338.         for (i = 0; i < 4; i++)
  339.           {
  340.         err |= specify_format (&group_format[i], b);
  341.         b += strlen (b) + 1;
  342.           }
  343.         if (err)
  344.           error ("conflicting #ifdef formats", 0, 0);
  345.       }
  346.       break;
  347.  
  348.     case 'e':
  349.       /* Make output that is a valid `ed' script.  */
  350.       specify_style (OUTPUT_ED);
  351.       break;
  352.  
  353.     case 'f':
  354.       /* Make output that looks vaguely like an `ed' script
  355.          but has changes in the order they appear in the file.  */
  356.       specify_style (OUTPUT_FORWARD_ED);
  357.       break;
  358.  
  359.     case 'F':
  360.       /* Show, for each set of changes, the previous line that
  361.          matches the specified regexp.  Currently affects only
  362.          context-style output.  */
  363.       add_regexp (&function_regexp_list, optarg);
  364.       break;
  365.  
  366.     case 'h':
  367.       /* Split the files into chunks of around 1500 lines
  368.          for faster processing.  Usually does not change the result.
  369.  
  370.          This currently has no effect.  */
  371.       break;
  372.  
  373.     case 'H':
  374.       /* Turn on heuristics that speed processing of large files
  375.          with a small density of changes.  */
  376.       heuristic = 1;
  377.       break;
  378.  
  379.     case 'i':
  380.       /* Ignore changes in case.  */
  381.       ignore_case_flag = 1;
  382.       break;
  383.  
  384.     case 'I':
  385.       /* Ignore changes affecting only lines that match the
  386.          specified regexp.  */
  387.       add_regexp (&ignore_regexp_list, optarg);
  388.       break;
  389.  
  390.     case 'l':
  391.       /* Pass the output through `pr' to paginate it.  */
  392.       paginate_flag = 1;
  393.       break;
  394.  
  395.     case 'L':
  396.       /* Specify file labels for `-c' output headers.  */
  397.       if (!file_label[0])
  398.         file_label[0] = optarg;
  399.       else if (!file_label[1])
  400.         file_label[1] = optarg;
  401.       else
  402.         fatal ("too many file label options");
  403.       break;
  404.       
  405.     case 'n':
  406.       /* Output RCS-style diffs, like `-f' except that each command
  407.          specifies the number of lines affected.  */
  408.       specify_style (OUTPUT_RCS);
  409.       break;
  410.  
  411.     case 'N':
  412.       /* When comparing directories, if a file appears only in one
  413.          directory, treat it as present but empty in the other.  */
  414.       entire_new_file_flag = 1;
  415.       break;
  416.  
  417.     case 'p':
  418.       /* Make context-style output and show name of last C function.  */
  419.       specify_style (OUTPUT_CONTEXT);
  420.       add_regexp (&function_regexp_list, "^[_a-zA-Z$]");
  421.       break;
  422.  
  423.     case 'P':
  424.       /* When comparing directories, if a file appears only in
  425.          the second directory of the two,
  426.          treat it as present but empty in the other.  */
  427.       unidirectional_new_file_flag = 1;
  428.       break;
  429.  
  430.     case 'q':
  431.       no_details_flag = 1;
  432.       break;
  433.  
  434.     case 'r':
  435.       /* When comparing directories, 
  436.          recursively compare any subdirectories found.  */
  437.       recursive = 1;
  438.       break;
  439.  
  440.     case 's':
  441.       /* Print a message if the files are the same.  */
  442.       print_file_same_flag = 1;
  443.       break;
  444.  
  445.     case 'S':
  446.       /* When comparing directories, start with the specified
  447.          file name.  This is used for resuming an aborted comparison.  */
  448.       dir_start_file = optarg;
  449.       break;
  450.  
  451.     case 't':
  452.       /* Expand tabs to spaces in the output so that it preserves
  453.          the alignment of the input files.  */
  454.       tab_expand_flag = 1;
  455.       break;
  456.  
  457.     case 'T':
  458.       /* Use a tab in the output, rather than a space, before the
  459.          text of an input line, so as to keep the proper alignment
  460.          in the input line without changing the characters in it.  */
  461.       tab_align_flag = 1;
  462.       break;
  463.  
  464.     case 'u':
  465.       /* Output the context diff in unidiff format.  */
  466.       specify_style (OUTPUT_UNIFIED);
  467.       break;
  468.  
  469.     case 'v':
  470.       fprintf (stderr, "GNU diff version %s\n", version_string);
  471.       break;
  472.  
  473.     case 'w':
  474.       /* Ignore horizontal whitespace when comparing lines.  */
  475.       ignore_all_space_flag = 1;
  476.       length_varies = 1;
  477.       break;
  478.  
  479.     case 'x':
  480.       add_exclude (optarg);
  481.       break;
  482.  
  483.     case 'X':
  484.       if (add_exclude_file (optarg) != 0)
  485.         pfatal_with_name (optarg);
  486.       break;
  487.  
  488.     case 'y':
  489.       /* Use side-by-side (sdiff-style) columnar output. */
  490.       specify_style (OUTPUT_SDIFF);
  491.       break;
  492.  
  493.     case 'W':
  494.       /* Set the line width for OUTPUT_SDIFF.  */
  495.       if (ck_atoi (optarg, &width) || width <= 0)
  496.         fatal ("column width must be a positive integer");
  497.       break;
  498.       
  499.     case 129:
  500.       sdiff_left_only = 1;
  501.       break;
  502.       
  503.     case 130:
  504.       sdiff_skip_common_lines = 1;
  505.       break;
  506.       
  507.     case 131:
  508.       /* sdiff-style columns output. */
  509.       specify_style (OUTPUT_SDIFF);
  510.       sdiff_help_sdiff = 1;
  511.       break;
  512.  
  513.     case 132:
  514.     case 133:
  515.     case 134:
  516.       specify_style (OUTPUT_IFDEF);
  517.       {
  518.         const char **form = &line_format[c - 132];
  519.         if (*form && strcmp (*form, optarg) != 0)
  520.           error ("conflicting line format", 0, 0);
  521.         *form = optarg;
  522.       }
  523.       break;
  524.  
  525.     case 135:
  526.     case 136:
  527.     case 137:
  528.     case 138:
  529.       specify_style (OUTPUT_IFDEF);
  530.       {
  531.         const char **form = &group_format[c - 135];
  532.         if (*form && strcmp (*form, optarg) != 0)
  533.           error ("conflicting group format", 0, 0);
  534.         *form = optarg;
  535.       }
  536.       break;
  537.  
  538.     case 139:
  539.       if (ck_atoi (optarg, &horizon_lines) || horizon_lines < 0)
  540.         fatal ("horizon must be a nonnegative integer");
  541.       break;
  542.  
  543.     default:
  544.       usage ();
  545.     }
  546.       prev = c;
  547.     }
  548.  
  549.   if (optind != argc - 2)
  550.     usage ();
  551.  
  552.  
  553.   {
  554.     /*
  555.      *    We maximize first the half line width, and then the gutter width,
  556.      *    according to the following constraints:
  557.      *    1.  Two half lines plus a gutter must fit in a line.
  558.      *    2.  If the half line width is nonzero:
  559.      *        a.  The gutter width is at least GUTTER_WIDTH_MINIMUM.
  560.      *        b.  If tabs are not expanded to spaces,
  561.      *        a half line plus a gutter is an integral number of tabs,
  562.      *        so that tabs in the right column line up.
  563.      */
  564.     int t = tab_expand_flag ? 1 : TAB_WIDTH;
  565.     int off = (width + t + GUTTER_WIDTH_MINIMUM) / (2*t)  *  t;
  566.     sdiff_half_width = max (0, min (off - GUTTER_WIDTH_MINIMUM, width - off)),
  567.     sdiff_column2_offset = sdiff_half_width ? off : width;
  568.   }
  569.  
  570.   if (output_style != OUTPUT_CONTEXT && output_style != OUTPUT_UNIFIED)
  571.     context = 0;
  572.   else if (context == -1)
  573.     /* Default amount of context for -c.  */
  574.     context = 3;
  575.  
  576.   if (output_style == OUTPUT_IFDEF)
  577.     {
  578.       int i;
  579.       for (i = 0; i < sizeof (line_format) / sizeof (*line_format); i++)
  580.     if (!line_format[i])
  581.       line_format[i] = "%l\n";
  582.       if (!group_format[OLD])
  583.     group_format[OLD]
  584.       = group_format[UNCHANGED] ? group_format[UNCHANGED] : "%<";
  585.       if (!group_format[NEW])
  586.     group_format[NEW]
  587.       = group_format[UNCHANGED] ? group_format[UNCHANGED] : "%>";
  588.       if (!group_format[UNCHANGED])
  589.     group_format[UNCHANGED] = "%=";
  590.       if (!group_format[CHANGED])
  591.     group_format[CHANGED] = concat (group_format[OLD],
  592.                     group_format[NEW], "");
  593.     }
  594.  
  595.   no_diff_means_no_output =
  596.     (output_style == OUTPUT_IFDEF ?
  597.       (!*group_format[UNCHANGED]
  598.        || (strcmp (group_format[UNCHANGED], "%=") == 0
  599.        && !*line_format[UNCHANGED]))
  600.      : output_style == OUTPUT_SDIFF ? sdiff_skip_common_lines : 1);
  601.  
  602.   switch_string = option_list (argv + 1, optind - 1);
  603.  
  604.   val = compare_files (NULL, argv[optind], NULL, argv[optind + 1], 0);
  605.  
  606.   /* Print any messages that were saved up for last.  */
  607.   print_message_queue ();
  608.  
  609.   if (ferror (stdout) || fclose (stdout) != 0)
  610.     fatal ("write error");
  611.   exit (val);
  612.   return val;
  613. }
  614.  
  615. /* Add the compiled form of regexp PATTERN to REGLIST.  */
  616.  
  617. static void
  618. add_regexp (reglist, pattern)
  619.      struct regexp_list **reglist;
  620.      char *pattern;
  621. {
  622.   struct regexp_list *r;
  623.   const char *m;
  624.  
  625.   r = (struct regexp_list *) xmalloc (sizeof (*r));
  626.   bzero (r, sizeof (*r));
  627.   r->buf.fastmap = (char *) xmalloc (256);
  628.   m = re_compile_pattern (pattern, strlen (pattern), &r->buf);
  629.   if (m != 0)
  630.     error ("%s: %s", pattern, m);
  631.  
  632.   /* Add to the start of the list, since it's easier than the end.  */
  633.   r->next = *reglist;
  634.   *reglist = r;
  635. }
  636.  
  637. static void
  638. usage ()
  639. {
  640.   fprintf (stderr, "Usage: %s [options] from-file to-file\n", program);
  641.   fprintf (stderr, "Options:\n\
  642.        [-abBcdefhHilnNpPqrstTuvwy] [-C lines] [-D name] [-F regexp]\n\
  643.        [-I regexp] [-L from-label [-L to-label]] [-S starting-file] [-U lines]\n\
  644.        [-W columns] [-x pattern] [-X pattern-file] [--exclude=pattern]\n\
  645.        [--exclude-from=pattern-file] [--ignore-blank-lines] [--context[=lines]]\n\
  646.        [--ifdef=name] [--show-function-line=regexp] [--speed-large-files]\n\
  647.        [--label=from-label [--label=to-label]] [--new-file]\n");
  648.   fprintf (stderr, "\
  649.        [--ignore-matching-lines=regexp] [--unidirectional-new-file]\n\
  650.        [--starting-file=starting-file] [--initial-tab] [--width=columns]\n\
  651.        [--text] [--ignore-space-change] [--minimal] [--ed] [--forward-ed]\n\
  652.        [--ignore-case] [--paginate] [--rcs] [--show-c-function] [--brief]\n\
  653.        [--recursive] [--report-identical-files] [--expand-tabs] [--version]\n");
  654.   fprintf (stderr, "\
  655.        [--ignore-all-space] [--side-by-side] [--unified[=lines]]\n\
  656.        [--left-column] [--suppress-common-lines] [--sdiff-merge-assist]\n\
  657.        [--old-line-format=format] [--new-line-format=format]\n\
  658.        [--unchanged-line-format=format]\n\
  659.        [--old-group-format=format] [--new-group-format=format]\n\
  660.        [--unchanged-group-format=format] [--changed-group-format=format]\n\
  661.        [--horizon-lines=lines]\n");
  662.   exit (2);
  663.  
  664. static int
  665. specify_format (var, value)
  666.      const char **var;
  667.      const char *value;
  668. {
  669.   int err = *var ? strcmp (*var, value) : 0;
  670.   *var = value;
  671.   return err;
  672. }
  673.  
  674. static void
  675. specify_style (style)
  676.      enum output_style style;
  677. {
  678.   if (output_style != OUTPUT_NORMAL
  679.       && output_style != style)
  680.     error ("conflicting specifications of output style", 0, 0);
  681.   output_style = style;
  682. }
  683.  
  684. /* Compare two files (or dirs) with specified names
  685.    DIR0/NAME0 and DIR1/NAME1, at level DEPTH in directory recursion.
  686.    (if DIR0 is 0, then the name is just NAME0, etc.)
  687.    This is self-contained; it opens the files and closes them.
  688.  
  689.    Value is 0 if files are the same, 1 if different,
  690.    2 if there is a problem opening them.  */
  691.  
  692. static int
  693. compare_files (dir0, name0, dir1, name1, depth)
  694.      char *dir0, *dir1;
  695.      char *name0, *name1;
  696.      int depth;
  697. {
  698.   struct file_data inf[2];
  699.   register int i;
  700.   int val;
  701.   int same_files;
  702.   int errorcount = 0;
  703.  
  704.   /* If this is directory comparison, perhaps we have a file
  705.      that exists only in one of the directories.
  706.      If so, just print a message to that effect.  */
  707.  
  708.   if (! ((name0 != 0 && name1 != 0)
  709.      || (unidirectional_new_file_flag && name1 != 0)
  710.      || entire_new_file_flag))
  711.     {
  712.       char *name = name0 == 0 ? name1 : name0;
  713.       char *dir = name0 == 0 ? dir1 : dir0;
  714.       message ("Only in %s: %s\n", dir, name);
  715.       /* Return 1 so that diff_dirs will return 1 ("some files differ").  */
  716.       return 1;
  717.     }
  718.  
  719.   /* Mark any nonexistent file with -1 in the desc field.  */
  720.   /* Mark unopened files (i.e. directories) with -2. */
  721.  
  722.   inf[0].desc = name0 == 0 ? -1 : -2;
  723.   inf[1].desc = name1 == 0 ? -1 : -2;
  724.  
  725.   /* Now record the full name of each file, including nonexistent ones.  */
  726.  
  727.   if (name0 == 0)
  728.     name0 = name1;
  729.   if (name1 == 0)
  730.     name1 = name0;
  731.  
  732. #ifndef AMIGA
  733.   inf[0].name = dir0 == 0 ? name0 : concat (dir0, "/", name0);
  734.   inf[1].name = dir1 == 0 ? name1 : concat (dir1, "/", name1);
  735. #else /* AMIGA */
  736.   {
  737.     int len;
  738.     if (dir0 != 0) len = strlen (dir0);
  739.     if (dir0 == 0 || len == 0)
  740.       inf[0].name = name0;
  741.     else if (dir0[len-1] == ':')
  742.       inf[0].name = concat (dir0, "", name0);
  743.     else
  744.       inf[0].name = concat (dir0, "/", name0);
  745.   }
  746.   {
  747.     int len;
  748.     if (dir1 != 0) len = strlen (dir1);
  749.     if (dir1 == 0 || len == 0)
  750.       inf[1].name = name1;
  751.     else if (dir1[len-1] == ':')
  752.       inf[1].name = concat (dir1, "", name1);
  753.     else
  754.       inf[1].name = concat (dir1, "/", name1);
  755.   }
  756. #endif /* AMIGA */
  757.  
  758.   /* Stat the files.  Record whether they are directories.  */
  759.  
  760.   for (i = 0; i <= 1; i++)
  761.     {
  762.       bzero (&inf[i].stat, sizeof (struct stat));
  763.       inf[i].dir_p = 0;
  764.  
  765.       if (inf[i].desc != -1)
  766.     {
  767.       int stat_result;
  768.  
  769.       if (strcmp (inf[i].name, "-") == 0)
  770.         {
  771.           inf[i].desc = 0;
  772.           inf[i].name = "Standard Input";
  773. #ifndef AMIGA
  774.           stat_result = fstat (0, &inf[i].stat);
  775. #else /* AMIGA */
  776.           stat_result = fake_stat_result (&inf[i].stat);
  777. #endif /* AMIGA */
  778.         }
  779.       else
  780.         stat_result = stat (inf[i].name, &inf[i].stat);
  781.  
  782.       if (stat_result != 0)
  783.         {
  784.           perror_with_name (inf[i].name);
  785.           errorcount = 1;
  786.         }
  787.       else
  788.         inf[i].dir_p = S_ISDIR (inf[i].stat.st_mode) && inf[i].desc != 0;
  789.     }
  790.     }
  791.  
  792.   if (name0 == 0)
  793.     inf[0].dir_p = inf[1].dir_p;
  794.   if (name1 == 0)
  795.     inf[1].dir_p = inf[0].dir_p;
  796.  
  797.   if (errorcount == 0 && depth == 0 && inf[0].dir_p != inf[1].dir_p)
  798.     {
  799.       /* If one is a directory, and it was specified in the command line,
  800.      use the file in that dir with the other file's basename.  */
  801.  
  802. #ifndef AMIGA
  803.       int fnm_arg = inf[0].dir_p;
  804.       int dir_arg = 1 - fnm_arg;
  805.       char *p = rindex (inf[fnm_arg].name, '/');
  806.       char *filename = inf[dir_arg].name
  807.     = concat (inf[dir_arg].name,  "/", (p ? p+1 : inf[fnm_arg].name));
  808. #else /* AMIGA */
  809.       int fnm_arg, dir_arg;
  810.       char *p1, *p2, *p;
  811.       char *filename;
  812.  
  813.       fnm_arg = inf[0].dir_p;
  814.       dir_arg = 1 - fnm_arg;
  815.       p1 = rindex (inf[fnm_arg].name, '/');
  816.       p2 = rindex (inf[fnm_arg].name, ':');
  817.       p = max (p1, p2);
  818.       if (*(inf[dir_arg].name + strlen (inf[dir_arg].name) - 1) == ':')
  819.         filename = inf[dir_arg].name
  820.           = concat (inf[dir_arg].name, "", (p ? p+1 : inf[fnm_arg].name));
  821.       else
  822.         filename = inf[dir_arg].name
  823.           = concat (inf[dir_arg].name, "/", (p ? p+1 : inf[fnm_arg].name));
  824. #endif /* !AMIGA */
  825.  
  826.       if (inf[fnm_arg].desc == 0)
  827.     fatal ("can't compare - to a directory");
  828.  
  829.       if (stat (filename, &inf[dir_arg].stat) != 0)
  830.     {
  831.       perror_with_name (filename);
  832.       errorcount = 1;
  833.     }
  834.       else
  835.     inf[dir_arg].dir_p = S_ISDIR (inf[dir_arg].stat.st_mode);
  836.     }
  837.  
  838.   if (errorcount)
  839.     {
  840.  
  841.       /* If either file should exist but does not, return 2.  */
  842.  
  843.       val = 2;
  844.  
  845.     }
  846.   else if ((same_files =    inf[0].stat.st_ino == inf[1].stat.st_ino
  847.              && inf[0].stat.st_dev == inf[1].stat.st_dev
  848.              && inf[0].desc != -1
  849.              && inf[1].desc != -1)
  850.        && no_diff_means_no_output)
  851.     {
  852.       /* The two named files are actually the same physical file.
  853.      We know they are identical without actually reading them.  */
  854.  
  855.       val = 0;
  856.     }
  857.   else if (inf[0].dir_p & inf[1].dir_p)
  858.     {
  859.       if (output_style == OUTPUT_IFDEF)
  860.     fatal ("-D option not supported with directories");
  861.  
  862.       /* If both are directories, compare the files in them.  */
  863.  
  864.       if (depth > 0 && !recursive)
  865.     {
  866.       /* But don't compare dir contents one level down
  867.          unless -r was specified.  */
  868.       message ("Common subdirectories: %s and %s\n",
  869.            inf[0].name, inf[1].name);
  870.       val = 0;
  871.     }
  872.       else
  873.     {
  874.       val = diff_dirs (inf, compare_files, depth);
  875.     }
  876.  
  877.     }
  878.   else if (inf[0].dir_p | inf[1].dir_p)
  879.     {
  880.       /* Perhaps we have a subdirectory that exists only in one directory.
  881.      If so, just print a message to that effect.  */
  882.  
  883.       if (inf[0].desc == -1 || inf[1].desc == -1)
  884.     {
  885.       if (recursive
  886.           && (entire_new_file_flag
  887.           || (unidirectional_new_file_flag && inf[0].desc == -1)))
  888.         val = diff_dirs (inf, compare_files, depth);
  889.       else
  890.         {
  891.           char *dir = (inf[0].desc == -1) ? dir1 : dir0;
  892.           message ("Only in %s: %s\n", dir, name0);
  893.           val = 1;
  894.         }
  895.     }
  896.       else
  897.     {
  898.       /* We have a subdirectory in one directory
  899.          and a file in the other.  */
  900.  
  901.       message ("%s is a directory but %s is not\n",
  902.            inf[1 - inf[0].dir_p].name, inf[inf[0].dir_p].name);
  903.  
  904.       /* This is a difference.  */
  905.       val = 1;
  906.     }
  907.     }
  908.   else if (no_details_flag
  909.        && inf[0].stat.st_size != inf[1].stat.st_size
  910.        && (inf[0].desc == -1 || S_ISREG (inf[0].stat.st_mode))
  911.        && (inf[1].desc == -1 || S_ISREG (inf[1].stat.st_mode)))
  912.     {
  913.       message ("Files %s and %s differ\n", inf[0].name, inf[1].name);
  914.       val = 1;
  915.     }
  916.   else
  917.     {
  918.       /* Both exist and neither is a directory.  */
  919.  
  920.       /* Open the files and record their descriptors.  */
  921.  
  922.       if (inf[0].desc == -2)
  923.     if ((inf[0].desc = open (inf[0].name, O_RDONLY, 0)) < 0)
  924.       {
  925.         perror_with_name (inf[0].name);
  926.         errorcount = 1;
  927.       }
  928.       if (inf[1].desc == -2)
  929.     if (same_files)
  930.       inf[1].desc = inf[0].desc;
  931.     else if ((inf[1].desc = open (inf[1].name, O_RDONLY, 0)) < 0)
  932.       {
  933.         perror_with_name (inf[1].name);
  934.         errorcount = 1;
  935.       }
  936.     
  937.       /* Compare the files, if no error was found.  */
  938.  
  939.       val = errorcount ? 2 : diff_2_files (inf, depth);
  940.  
  941.       /* Close the file descriptors.  */
  942.  
  943.       if (inf[0].desc >= 0 && close (inf[0].desc) != 0)
  944.     {
  945.       perror_with_name (inf[0].name);
  946.       val = 2;
  947.     }
  948.       if (inf[1].desc >= 0 && inf[0].desc != inf[1].desc
  949.       && close (inf[1].desc) != 0)
  950.     {
  951.       perror_with_name (inf[1].name);
  952.       val = 2;
  953.     }
  954.     }
  955.  
  956.   /* Now the comparison has been done, if no error prevented it,
  957.      and VAL is the value this function will return.  */
  958.  
  959.   if (val == 0 && !inf[0].dir_p)
  960.     {
  961.       if (print_file_same_flag)
  962.     message ("Files %s and %s are identical\n",
  963.          inf[0].name, inf[1].name);
  964.     }
  965.   else
  966.     fflush (stdout);
  967.  
  968. #ifndef AMIGA
  969.   if (dir0 != 0)
  970.     free (inf[0].name);
  971.   if (dir1 != 0)
  972.     free (inf[1].name);
  973. #else /* AMIGA */
  974.   if (dir0 != 0 && strlen(dir0) != 0)
  975.     free (inf[0].name);
  976.   if (dir1 != 0 && strlen(dir1) != 0)
  977.     free (inf[1].name);
  978. #endif /* !AMIGA */
  979.  
  980.   return val;
  981. }
  982.  
  983. #ifdef AMIGA
  984. static int fake_stat_result (sbuf)
  985.      struct stat *sbuf;
  986. {
  987.   time_t cur_time;
  988.  
  989.   time (&cur_time);
  990.   sbuf->st_dev = 0;
  991.   sbuf->st_ino = 0;
  992.   sbuf->st_mode = S_IREAD;
  993.   sbuf->st_nlink = 1;
  994.   sbuf->st_uid = 0;
  995.   sbuf->st_gid = 0;
  996.   sbuf->st_size = 0;
  997.   sbuf->st_ctime = sbuf->st_atime = sbuf->st_mtime = cur_time;
  998.   return 0;
  999. }
  1000. #endif /* AMIGA */
  1001.